/*
	osgLua: use Lua to access dynamically to osg using osgIntrospection
	Copyright(C) 2006 Jose L. Hidalgo Valiño (PpluX) (pplux at pplux.com)

    This library is open source and may be redistributed and/or modified under  
    the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
    (at your option) any later version.  The full license is in LICENSE file
    included with this distribution, and on the openscenegraph.org website.
    
    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    OpenSceneGraph Public License for more details.
*/

#include "Value.h"
#include "Type.h"

#include <osgIntrospection/Reflection>
#include <osgIntrospection/MethodInfo>
#include <osgIntrospection/ConstructorInfo>
#include <osgIntrospection/Utility>
#include <osgIntrospection/Value>
#include <osgIntrospection/Type>

#include <iostream>

extern "C" {
	#include <lualib.h>
	#include <lauxlib.h>
}

namespace osgLua {

	namespace Type
	{
		void push(lua_State *L, const char *TypeName, bool);
		int index(lua_State *L);
		int call(lua_State *L);
	}

	int staticMethodCall(lua_State *L)
	{
		int top = lua_gettop(L);
		const char *tname  = lua_tostring(L, lua_upvalueindex(1));
		const char *method = lua_tostring(L, lua_upvalueindex(2));
		try
		{
			const osgIntrospection::Type &type = 
				osgIntrospection::Reflection::getType(tname);

			osgIntrospection::ValueList vl;
			for(int i = 1; i <= top; ++i)
			{
				vl.push_back( getValue(L, i) );
			}

			osgIntrospection::ParameterInfoList params;
			const osgIntrospection::MethodInfo *mi =
				type.getCompatibleMethod(method,vl,false);

			if (mi)
			{
				Value::push(L, mi->invoke(vl));
				return 1;
			}
			else
			{
				luaL_error(L, "Class %s do not have static method %s",
					tname,method);
			}
		}
		catch (osgIntrospection::Exception &e)
		{
			luaL_error(L,"[%s:%d] %s",__FILE__,__LINE__,e.what().c_str());
		}
		return 0;
	}

	void Type::push(lua_State *L, const char *TypeName, bool __newindex)
	{
#if 0
		lua_newtable(L); // table
		lua_newtable(L); // metatable
		lua_pushstring(L, TypeName);
		lua_pushcclosure(L, Type::index, 1);
		lua_setfield(L, -2, "__index");
		lua_pushstring(L, TypeName);
		lua_pushcclosure(L, Type::call, 1);
		lua_setfield(L, -2, "__call");
		lua_setmetatable(L, -2);
#endif
		//registry version
		lua_getfield(L, LUA_REGISTRYINDEX, TypeName);
		if (!lua_isnil(L, -1))
		{
			return;
		}
		lua_newtable(L); // table
		lua_newtable(L); // metatable
		lua_pushstring(L, TypeName);
		lua_pushcclosure(L, Type::index, 1);
		lua_setfield(L, -2, "__index");
		lua_pushstring(L, TypeName);
		lua_pushcclosure(L, Type::call, 1);
		lua_setfield(L, -2, "__call");
		if (__newindex)
		{
			lua_pushstring(L, TypeName);
			lua_pushcclosure(L, Type::newindex, 1);
			lua_setfield(L, -2, "__newindex");
		}

		lua_newtable(L);//value.metatable
		lua_pushcfunction(L, Value::gc);
		lua_setfield(L, -2, "__gc");	
		lua_pushcfunction(L, Value::index);
		lua_setfield(L, -2, "__index");	
		lua_pushcfunction(L, Value::tostring);
		lua_setfield(L, -2, "__tostring");
		luaL_newmetatable(L, "osgLua::Value");
		lua_setfield(L, -2, "_signature");

		lua_setfield(L, -2, "_valuemeta");//type.metatable['_valuemeta'] = value.metatable

		lua_setmetatable(L, -2);
		lua_pushvalue(L,-1);//copy
		lua_setfield(L, LUA_REGISTRYINDEX, TypeName);
		printf("register metatable for :%s\n", TypeName);
	}

	int Type::newindex(lua_State *L)
	{
		lua_getmetatable(L, 1);
		lua_getfield(L, -1, "_valuemeta");
		lua_pushvalue(L, 2);//key
		lua_pushvalue(L, 3);//value
		lua_rawset(L, -3);
		return 0;
	}

	int Type::index(lua_State *L)
	{
		const char *name = luaL_checkstring(L, 2);

		/*
		//try rawget field first
		lua_getfield(L, 1, name);
		if (!lua_isnil(L, -1))
		{
			return 1;
		}
		*/
		std::string tbase( lua_tostring(L, lua_upvalueindex(1)) );
		std::string base(tbase+"::"+name);
		
		// traverse the namespace
		try
		{
			const osgIntrospection::Type &type = 
				osgIntrospection::Reflection::getType(base);
			base = type.getQualifiedName();

			// is it an enum ?
			if (type.isEnum())
			{
				// create a table with the values
				const osgIntrospection::EnumLabelMap &map = 
					type.getEnumLabels();

				lua_newtable(L);
				for( osgIntrospection::EnumLabelMap::const_iterator
					i = map.begin(); i != map.end(); ++i)
				{
					lua_pushinteger(L, i->first);
					lua_setfield(L, -2, i->second.c_str());
				}
				return 1;
			}
			
			// if not... push the type
			Type::push(L, base.c_str());
			return 1;
		}
		catch (osgIntrospection::TypeNotFoundException&)
		{
			// if base is not a valid type... supose that
			// tbase.name is an static function 
			lua_pushstring(L,tbase.c_str());
			lua_pushstring(L,name);
			lua_pushcclosure(L, staticMethodCall, 2);
			return 1;
		}

	}

	int Type::call(lua_State *L)
	{
		int top = lua_gettop(L);
		std::string name( lua_tostring(L, lua_upvalueindex(1)) );
		try
		{
			const osgIntrospection::Type &type = 
				osgIntrospection::Reflection::getType(name);
		
			osgIntrospection::ValueList vl;
			for(int i = 2; i <= top; ++i)
			{
				vl.push_back( getValue(L, i) );
			}

			osgIntrospection::Value returnedval = type.createInstance(vl);

			Value::push(L, returnedval);
			return 1;
		}
		catch (osgIntrospection::Exception &e)
		{
			luaL_error(L,"[%s:%d] %s",__FILE__,__LINE__,e.what().c_str());
		}
		return 0;
	}

} // end of osgLua namespace
